package com.wen.system.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.pagehelper.PageHelper;
import com.wen.common.dto.LoginDTO;
import com.wen.common.dto.UserAddressDTO;
import com.wen.common.dto.UserDTO;
import com.wen.common.enums.CodeEnum;
import com.wen.common.exception.CustomException;
import com.wen.common.pojo.system.Permission;
import com.wen.common.pojo.system.Role;
import com.wen.common.pojo.system.User;
import com.wen.common.pojo.system.UserRole;
import com.wen.common.utils.*;
import com.wen.system.jwt.JwtToken;
import com.wen.system.mapper.RoleMapper;
import com.wen.system.mapper.TkMybatisMapperTest;
import com.wen.system.mapper.UserMapper;
import com.wen.system.service.OAuthService;
import com.wen.system.service.RoleService;
import com.wen.system.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {

    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
    private static final AtomicInteger RETRY_NUM = new AtomicInteger(1);
    @Autowired
    private UserMapper userMapper;
    //    @Autowired
    private TkMybatisMapperTest tkMybatisMapperTest;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private RoleService roleService;
    @Autowired
    private OAuthService oAuthService;
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private RedisUtil redisUtil;

    @Override
    public User findUserByName(String name) {

        return userMapper.findUserByName(name);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertUser(User user) {
        return userMapper.insert(user);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public int update(User user) {
        return userMapper.updateById(user);
    }

    @Override
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }

    @Override
    public Page<UserDTO> findUserPage(int pageNum, int pageSize, String search) {

        LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().orderByAsc(User::getId);
        if (StrUtil.isNotBlank(search)) {
            wrapper.like(User::getNickName, search);
        }
        Page<User> userPage = userMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);

        /*
         * 封装userDtoPage
         *  思路：
         * 1.取出userRecords ，添加角色数组
         * 2.替换page ，封装到userDtoPage
         * */

        Page<UserDTO> userDtoPage = new Page<>(pageNum, pageSize);
        BeanUtils.copyProperties(userPage, userDtoPage);

        ArrayList<UserDTO> userDTORecords = new ArrayList<>();

        List<User> users = userPage.getRecords();
        // TODO: 把实体快捷装成dto/vo
        List<UserDTO> userDTOS = users.stream().map(UserDTO::new).collect(Collectors.toList());
        // 1.1遍历用户 ，添加角色数组
        for (UserDTO userDto : userDTOS) {
            ArrayList<Long> list = new ArrayList<>();// 角色数组 ,放到循环里面 当前用户的角色

            // 创造一个userDto >>>
//            UserDto userDto = new UserDto();
//            BeanUtils.copyProperties(user,userDto);

            // 根据id查用户角色id
            List<UserRole> userRoleList = userMapper.getUserRoleByUserId(userDto.getId());
            for (UserRole userRole : userRoleList) {
                Long roleId = userRole.getRoleId();
                list.add(roleId);
            }
            // 设置当前用户的角色集合
            userDto.setRoles(list);
            // 当前带角色的用户添加到Records中
            userDTORecords.add(userDto);
        }
        userDtoPage.setRecords(userDTORecords);

        return userDtoPage;
    }

    @Override
    public User login(LoginDTO loginDTO) {
        JwtToken token;
        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();
        // 密钥为盐，md5的盐值
        String md5Pwd = Md5Util.md5Str(password);

        if (redisUtil.hasKey(Constant.RedisKey.USER_LOCK_PREFIX + username))
            throw new CustomException(CodeEnum.USER_LOCK.getResultCode(), CodeEnum.USER_LOCK.getResultMsg());

        User user = userMapper.login(username, md5Pwd);
        if (user == null) record2Redis(username);
        // 生成jwt
        assert user != null;
        String jwt = jwtUtil.sign(user.getUserName(), jwtUtil.getSecret());
        // token
        token = new JwtToken(jwt);
        token.setIat(jwtUtil.getIssuedAt(jwt));
        token.setExp(jwtUtil.getExpirationDate(jwt));

        Subject subject = SecurityUtils.getSubject();
        subject.login(token); // 在这里调用，realm里的shiro异常会被全局捕获

        User activeUser = (User) subject.getPrincipal();
        // 保存到数据库中
        saveToken(activeUser);
        return activeUser;
    }

    private void record2Redis(String username) {
        // 过期时间
        int lockTime = redisUtil.hHasKey(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_TIME)
                ? (int) redisUtil.hget(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_TIME)
                : Constant.DEFAULT_USER_LOCK_TIME;
        log.info("lock time : {}", lockTime);

        // 失败次数
        int lockNum = redisUtil.hHasKey(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_NUM)
                ? (int) redisUtil.hget(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_NUM)
                : Constant.DEFAULT_USER_MAX_RETRY_NUM;
        log.info("lock num : {}", lockNum);
        /* 加锁 */

        log.info("retry num : {}", RETRY_NUM.get());
        if (RETRY_NUM.getAndIncrement() > lockNum) {
            redisUtil.set(Constant.RedisKey.USER_LOCK_PREFIX + username, RETRY_NUM.get(),
                    lockTime, TimeUnit.SECONDS);

            RETRY_NUM.set(1);
            throw new CustomException(CodeEnum.USER_LOCK.getResultCode(), CodeEnum.USER_LOCK.getResultMsg());
        }
        throw new CustomException(CodeEnum.USER_NAME_PASSWD_ERROR.getResultCode(), CodeEnum.USER_NAME_PASSWD_ERROR.getResultMsg());
    }

    @Override
    public ResponseResult<?> modifyUserLockTime(int lockTime) {
        if (redisUtil.hHasKey(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_TIME)) {
            redisUtil.hdel(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_TIME);
        }
        redisUtil.hset(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_TIME, lockTime, 0, TimeUnit.SECONDS);
        log.info("modify lock time : {}", lockTime);
        return ResponseResult.success();
    }

    @Override
    public ResponseResult<?> modifyUserLockNum(int lockNum) {
        if (redisUtil.hHasKey(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_NUM)) {
            redisUtil.hdel(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_NUM);
        }
        redisUtil.hset(Constant.RedisKey.USER_LOCK_CACHE, Constant.RedisKey.USER_LOCK_NUM, lockNum, 0, TimeUnit.SECONDS);
        log.info("modify lock num : {}", lockNum);
        return ResponseResult.success();
    }


    private void saveToken(User loginUser) {
        String username = loginUser.getUserName();
        String jwt = loginUser.getToken();
        long expireTime = jwtUtil.getExpireTimeProp();
        redisUtil.hset(Constant.RedisKey.USER_TOKEN + username, Constant.PREFIX_ACCESS_TOKEN,
                jwt, expireTime, TimeUnit.SECONDS);
    }

    @Override
    public List<UserAddressDTO> countByAddress() {
        return userMapper.countByAddress();
    }

    @Override
    public Set<String> getRoleNameByUserId(Long id) {
        HashSet<String> set = new HashSet<>();
        List<UserRole> userRoleList = userMapper.getUserRoleByUserId(id);
        for (UserRole userRole : userRoleList) {
            Long roleId = userRole.getRoleId();
            Role role = roleMapper.selectById(roleId);
            set.add(role.getName());
        }
        return set;
    }

    @Override
    public List<Permission> getPermissionByUserId(Long id) {
        List<Permission> permissionList = new ArrayList<>();

        List<UserRole> userRoles = userMapper.getUserRoleByUserId(id);
        for (UserRole userRole : userRoles) {
            Long roleId = userRole.getRoleId();
            List<Permission> permission = roleService.findPermissionByRoleId(roleId);
            permissionList.addAll(permission);
        }
        // 去重，将查出来的权限菜单去重 （可以将权限放入set集合 就不用去重了）
        return permissionList.stream().distinct().collect(Collectors.toList());
    }


    @Override
    public List<Role> getRoleByUserId(Long userId) {
        List<Role> roleList = new ArrayList<>();
        List<UserRole> userRoles = userMapper.getUserRoleByUserId(userId);
        for (UserRole userRole : userRoles) {
            Long roleId = userRole.getRoleId();
            Role role = roleMapper.selectById(roleId);
            roleList.add(role);
        }
        return roleList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult<?> changeUserRole(UserDTO userDTO) {
        // 先根据用户id删除所有的用户跟角色的绑定关系
        userMapper.deleteUserRoleByUserId(userDTO.getId());
        // 遍历user的角色数组
        for (Long roleId : userDTO.getRoles()) {
            // 插入角色id
            userMapper.insertUserRole(userDTO.getId(), roleId);
        }
        List<Role> roles = this.getRoleByUserId(userDTO.getId());
        return ResponseResult.success(roles);
    }

    @Override
    public ResponseResult<?> deleteBatch(List<Long> ids) {
        int i = userMapper.deleteBatchIds(ids);
        // 根据id查出角色 ，如果有 删除角色id
        ids.forEach(id -> userMapper.deleteUserRoleByUserId(id));
        return ResponseResult.success();
    }

    /**
     * tk.mybatis包的使用
     */
    @Override
    public Page<UserDTO> findUserPage(Integer pageNum, Integer pageSize, UserDTO userDto) {

        //已经拥有的
        PageHelper.startPage(pageNum, pageSize);
        Example o = new Example(User.class);

        String username = userDto.getUserName();
        String nickname = userDto.getNickName();
        String sex = userDto.getSex();
        String email = userDto.getEmail();
        String address = userDto.getAddress();
        Integer age = userDto.getAge();
        String phoneNumber = userDto.getPhoneNumber();
        String status = userDto.getStatus();
        List<Long> roles = userDto.getRoles();
        Example.Criteria criteria = o.createCriteria();
        if (username != null && !"".equals(username)) {
            criteria.andLike("username", "%" + username + "%");
        }
        if (nickname != null && !"".equals(nickname)) {
            criteria.andLike("nick_name", "%" + nickname + "%");
        }
        if (email != null && !"".equals(email)) {
            criteria.andLike("email", "%" + email + "%");
        }
        if (sex != null) {
            criteria.andEqualTo("sex", sex);
        }
        if (address != null && !"".equals(address)) {
            criteria.andLike("address", "%" + address + "%");
        }

        List<UserDTO> dtoList = tkMybatisMapperTest.selectByExample(o);


        Page<UserDTO> userDtoPage = new Page<>(pageNum, pageSize);
        userDtoPage.setRecords(dtoList);
        return userDtoPage;
    }

    @Override
    public String createQrImg() {
        // todoo:
        return UUID.randomUUID().toString();
    }
}
